import { initSdkEnv } from "../env";

import { _decorator, Component, Node, Prefab, instantiate, Camera, Label, TiledUserNodeData, EditBoxComponent, EditBox, Widget, UITransform, ScrollView, Vec2, v2 } from 'cc';
const { ccclass, property } = _decorator;

import { FrameSyncExecutor } from "../common/FrameSyncExecutor";
import { PlayerData } from "./PlayerData";
import { PlayerComponent } from "./PlayerComponent";
import { InputType } from "./PlayerData";
import { JoystickComponent } from "../common/ui/JoystickComponent";
import { IMoveDirection } from "../common/ui/BaseJoystick";
import { SDKFrameSyncConnectAdp } from "../common/SDKFrameSyncConnectAdp";
import { MatcherKeys, Game, Room, EMatchFromType, ISingleMatcherParams, IMatchResult, IPlayerInfo, EPlayerInputFrameType, IAfterFrames, IFramePlayerInput, IPlayerInputOperate, delay, IResult, Result, ERoomMsgRecvType, EFrameSyncState, IRoomInfo, IPlayerInfoPara } from "tsgf-sdk";
import { PlayerNameFlagComponent } from "./PlayerNameFlagComponent";
import { DemoClient } from "tsgf-dev-demo-client";


@ccclass('Demo1SceneManager')
export class Demo1SceneManager extends Component {

    demoClient!: DemoClient;

    @property(Node)
    ViewLogin!: Node;
    @property(Node)
    ViewHall!: Node;
    @property(Node)
    ViewRoom!: Node;
    @property(Node)
    ViewRoomGameView!: Node;
    @property(Node)
    ViewRoomBg!: Node;
    @property(Node)
    ViewRoomShowPlayers!: Node;
    @property(Node)
    ViewRoomShowMsg!: Node;
    @property(Node)
    ViewRoomBtnStartGame!: Node;
    @property(Node)
    ViewRoomBtnStopGame!: Node;
    @property(Node)
    ViewGame3D!: Node;

    @property(Node)
    ViewLoading!: Node;
    @property(Label)
    ViewLoadingText!: Label;
    @property(Node)
    ViewTips!: Node;
    @property(Label)
    ViewTipsLabel!: Label;

    @property(Node)
    PlayersContainer?: Node;
    @property({ type: Node, tooltip: "存放所有Flag的UI节点" })
    GameObjFlagUI?: Node;
    @property(Prefab)
    PlayerPrefab?: Prefab;
    @property(Prefab)
    PlayerNameFlagPrefab?: Prefab;

    @property(Camera)
    MainCamera!: Camera;

    @property(JoystickComponent)
    Joystick!: JoystickComponent;

    @property(EditBox)
    PlayerShowName!: EditBox;
    @property(EditBox)
    InpJoinRoomId!: EditBox;
    @property(EditBox)
    ShowRoomId!: EditBox;
    @property(EditBox)
    InpRoomMsg!: EditBox;
    @property(Label)
    ShowRoomPlayers!: Label;
    @property(Label)
    ShowRoomMsg!: Label;
    @property(ScrollView)
    ShowRoomMsgScrollView!: ScrollView;
    @property(Node)
    BtnDismissRoom!: Node;
    @property(Node)
    BtnLeaveRoom!: Node;

    playerShowNameLibs: string[] = ['一块砖', '两百块', '小马哥', '鸭子', 'summer', 'duck', '哎哟跑得快', '一表人渣', '逆天小学生', '软软', 'Lucky', '英雄', '空子许', '小发丝', '画扇夜', '残局', '残影', '空城', '伤城', '离歌', '村里一朵花', '曲终人散', '划船不用桨', '留下一道痕', '疯子', '神', '霸气', '听弦断', '眉眼如初', '暮雪', '爱迪不能生', '耐法莉安', '鹊桥离恨', '温玉琳琅', '唇齿柔情', '爱过人渣骂过三八', '初吻给了香烟', '诗寒无凡事', '城空', '落花人'];

    frameIndex = 0;

    playerId?: string;
    playerToken?: string;
    playerInfoPara!: IPlayerInfoPara;
    frameSyncExecutor!: FrameSyncExecutor;
    allPlayers: { [playerId: string]: PlayerData } = {};
    myPlayer?: PlayerComponent;
    tmpV2: Vec2 = v2();

    onLoad() {
        // 初始化 tsgf-sdk 的环境实现(小程序和浏览器是区分开的)
        initSdkEnv();

        // 初始化tsgf内置的简单版用户服务的客户端
        this.demoClient = new DemoClient(typeof (demoServerUrl) === "undefined" ? "http://127.0.0.1:7901/" : demoServerUrl);
    }

    async start() {

        Room.ins.events.onReconnectStart((currTryCount) => {
            this.showLoading((currTryCount > 0 ? `[${currTryCount}]` : '') + '断线重连中...');
        });
        Room.ins.events.onReconnectResult((succ, err) => {
            this.hideLoading();
            //断线重连有结果了
            if (succ) {
                //成功连上,进入房间流程
                this.enterRoom();
            } else {
                //不再重连尝试了!表示彻底断开!(大多来自服务端的拒绝重试,超时之类)
                //退出房间到大厅
                this.exitRoom();
            }
        });

        Room.ins.events.onPlayerJoinRoom((player, roomInfo) => {
            this.ShowRoomPlayers.string = roomInfo.playerList.map(p => p.showName).join(';   ');
            this.appendRoomMsg(`[${player.showName}] 进入房间`);
        });
        Room.ins.events.onPlayerLeaveRoom((player, roomInfo) => {
            this.ShowRoomPlayers.string = roomInfo.playerList.map(p => p.showName).join(';   ');
            this.appendRoomMsg(`[${player.showName}] 离开房间`);
        });
        Room.ins.events.onDismissRoom((roomInfo) => {
            this.appendRoomMsg(`房间被解散!`);
            this.exitRoom();
        });
        Room.ins.events.onRecvRoomMsg((roomMsg) => {
            let recvType = '';
            switch (roomMsg.recvType) {
                case ERoomMsgRecvType.ROOM_ALL:
                    recvType = '[所有人]';
                    break;
                case ERoomMsgRecvType.ROOM_OTHERS:
                    recvType = '[其他人]';
                    break;
                case ERoomMsgRecvType.ROOM_SOME:
                    recvType = '[你]';
                    break;
            }
            this.appendRoomMsg(`[${roomMsg.fromPlayerInfo.showName}] 对 ${recvType} 说:${roomMsg.msg}`);
        });
        Room.ins.events.onStartFrameSync((r, player) => {
            this.appendRoomMsg(`[${player.showName}] 开始了游戏!`);
            //开始
            this.onStartGame();
        });
        Room.ins.events.onStopFrameSync((r, player) => {
            this.appendRoomMsg(`[${player.showName}] 结束了游戏!`);
            //停止
            this.onStopGame();
        });

        this.frameSyncExecutor = new FrameSyncExecutor(
            new SDKFrameSyncConnectAdp(),
            'inputType',
            this,
            (dt, frameIndex) => this.executeOneFrame(dt, frameIndex),
            () => this.allPlayers);

        this.stopGame();
        this.Joystick!.onmove = (dir) => {
            this.onJoysickMoveStart(dir);
        };
        this.Joystick!.onmoveend = () => {
            this.onJoysickMoveEnd();
        };

        this.viewStep(1);
        this.PlayerShowName.string = this.playerShowNameLibs[Math.floor(Math.random() * this.playerShowNameLibs.length)];
    }

    showTips(text: string, hideLoading = true) {
        this.ViewTipsLabel.string = text;
        this.ViewTips.active = true;
        if (hideLoading) this.hideLoading();
    }
    closeTips() {
        this.ViewTips.active = false;
    }

    showLoading(text?: string) {
        this.ViewLoadingText.string = text ?? '加载中，请稍后';
        this.ViewLoading.active = true;
    }
    hideLoading() {
        this.ViewLoading.active = false;
    }

    /**
     * 
     * @param step 1登录,2大厅,3房间,4游戏
     */
    viewStep(step: number) {
        this.ViewLogin.active = step == 1;
        this.ViewHall.active = step == 2;
        this.ViewRoom.active = step == 3 || step == 4;
        this.ViewRoomGameView.active = step == 4;
        this.ViewRoomBtnStartGame.active = step == 3;
        this.ViewRoomBtnStopGame.active = step == 4;
        this.ViewRoomBg.active = step == 3;
        this.ViewRoomShowPlayers.active = step == 3;
        //this.ViewRoomShowMsg.active = step == 3 || step == 4;
        this.ViewGame3D.active = step == 4;
    }

    async onLoginClick() {
        this.playerInfoPara = {
            showName: this.PlayerShowName.string,
        };
        let tmpOpenId = this.playerInfoPara.showName + Date.now();
        this.showLoading();

        // 这里使用tsgf内置的简单版用户服务, 完成tsgf的用户授权, 获得玩家授权信息
        //  demo里用输入的昵称+随机生成的用户id, 即每次登录视作[新用户]
        //  正式的项目, 这里应该获得用户登录信息, 再由自己的用户服务去获得玩家授权(调用授权接口)
        //    比如发的平台是[微信小游戏], 那么这里要调用微信授权,获得openid, 调用用户服务, 用户服务将openid匹配为(新/老)用户, 用户服务在服务端调用tsgf的玩家授权接口, 传入用户的唯一标识和昵称, 获得玩家授权信息
        let result = await this.demoClient.playerAuth(tmpOpenId, this.playerInfoPara.showName);
        this.hideLoading();
        if (!result.succ) {
            return this.showTips(result.err);
        }

        //初始化SDK
        // 为了保证通讯安全，必须使用tsgf玩家的授权信息（玩家id和token），才能连上tsgf服务器。
        var hallSvUrl = typeof (hallServerUrl) === "undefined" ? "http://127.0.0.1:7100/" : hallServerUrl;
        Game.ins.init(hallSvUrl, result.data.playerId, result.data.playerToken);

        this.playerId = result.data.playerId;
        this.playerToken = result.data.playerToken;

        //进入大厅
        this.viewStep(2);
    }


    /**
     * 开始玩家匹配(基础混战),直到结果返回
     * @date 2022/5/16 - 17:47:46
     *
     * @protected
     * @async
     * @param {string[]} playerIds
     * @param {number} maxPlayers
     * @param {number} minPlayers
     * @returns {Promise<IResult<IMatchResult>>}
     */
    protected async startPlayersMatchBaseMelee(playerIds: string[], minPlayers: number, maxPlayers: number): Promise<IResult<IMatchResult>> {
        return await new Promise(async (resolve) => {
            let hasResult = false;
            let reqMatchRet = await Room.ins.requestMatchFromPlayers({  // 玩家匹配的基础方法, 可以定义更复杂的匹配逻辑
                matchFromType: EMatchFromType.Player, // 玩家匹配类型
                matchFromInfo: {
                    playerIds: playerIds, // 要匹配的玩家id, 通常只要传[当前玩家id]即可
                },
                maxPlayers: maxPlayers, // 房间最大玩家数，不同的最大玩家数不会相互匹配到
                matcherKey: MatcherKeys.Single, // 单人玩家匹配模式（即无组队）matcherParams 类型为 ISingleMatcherParams
                matcherParams: {
                    minPlayers: minPlayers, // 最少要匹配到几个玩家才创建房间
                    resultsContinueRoomJoinUsMatch: true, // 匹配出房间后（满足最少玩家数），是否可以继续匹配进人
                } as ISingleMatcherParams, // 这里用 `as` 接口的写法，可以让字段获得vscode中的注释提示
            }, (result) => {
                if (!hasResult) {
                    hasResult = true;
                    return resolve(result);// 匹配成功后, 会拿到匹配的房间（房间和服务器相关信息）, 需要调用 Room.ins.joinRoomByServer 加入房间
                }
            });
            if (!reqMatchRet.succ) {
                if (!hasResult) {
                    hasResult = true;
                    return resolve(Result.buildErr(reqMatchRet.err, reqMatchRet.code));
                }
            }
        });
    }



    /**
     * 由匹配进入游戏房间(成功则会初始化gameClient)
     * @date 2022/5/16 - 17:50:41
     *
     * @protected
     * @async
     * @param {IMatchResult} matchResult
     * @returns {Promise<IResult<IRoomInfo>>}
     */
    protected async matchEnterRoom(matchResult: IMatchResult): Promise<IResult<IRoomInfo>> {
        let ret = await Room.ins.joinRoomByServer(matchResult.gameServerUrl, this.playerInfoPara,
            matchResult.roomId, matchResult.matchPlayerResults.find(r => r.playerId === this.playerId).teamId);
        return ret;
    }

    /**
     * 由创建进入游戏房间(成功则会初始化gameClient)
     * @date 2022/5/16 - 17:53:56
     *
     * @protected
     * @async
     * @returns {*}
     */
    protected async createRoomEnter(maxPlayers: number): Promise<IResult<IRoomInfo>> {
        let ret = await Room.ins.createRoom(this.playerInfoPara, {
            roomName: '手动创建的房间',
            ownerPlayerId: this.playerId!,
            maxPlayers: maxPlayers,
            isPrivate: true,
        });
        if (!ret.succ) {
            return Result.buildErr(ret.err, ret.code);
        }
        return ret;
    }
    /**
     * 加入别人创建的游戏房间(成功则会初始化gameClient)
     * @date 2022/5/16 - 17:53:56
     *
     * @protected
     * @async
     * @returns {*}
     */
    protected async joinRoomEnter(roomId: string): Promise<IResult<IRoomInfo>> {
        let ret = await Room.ins.joinRoom(this.playerInfoPara, roomId);
        if (!ret.succ) {
            return Result.buildErr(ret.err, ret.code);
        }
        return ret;
    }

    async onCreateRoomClick() {
        this.showLoading();
        let enterRoomRet = await this.createRoomEnter(10);
        if (!enterRoomRet.succ) {
            return this.showTips(enterRoomRet.err);
        }
        //全部成功了,隐藏loading
        this.hideLoading();
        this.enterRoom();
    }
    async onJoinRoomClick() {
        this.showLoading();
        let enterRoomRet = await this.joinRoomEnter(this.InpJoinRoomId.string);
        if (!enterRoomRet.succ) {
            return this.showTips(enterRoomRet.err);
        }
        //全部成功了,隐藏loading
        this.hideLoading();
        this.enterRoom();
    }

    private async startBaseMeleeMatchEnterRoomFromCurrPlayer(minPlayers: number, maxPlayers: number): Promise<void> {
        this.showLoading(`匹配${minPlayers === maxPlayers ? minPlayers : (minPlayers + '~' + maxPlayers)}人房间中(无限等待)`);
        let matchResult = await this.startPlayersMatchBaseMelee([this.playerId!], minPlayers, maxPlayers);
        if (!matchResult.succ) {
            return this.showTips(matchResult.err);
        }
        let enterRoomRet = await this.matchEnterRoom(matchResult.data);
        if (!enterRoomRet.succ) {
            return this.showTips(enterRoomRet.err);
        }
        //全部成功了,隐藏loading
        this.hideLoading();
        this.enterRoom();
    }

    async onStartMatch1v1Click() {
        await this.startBaseMeleeMatchEnterRoomFromCurrPlayer(2, 2);
    }
    async onStartMatch2_20MeleeClick() {
        await this.startBaseMeleeMatchEnterRoomFromCurrPlayer(2, 20);
    }
    async onStartMatch1_10MeleeClick() {
        //就是现在没其他人匹配先创建房间先进,后面有其他人点了这种匹配,也进这个房间
        await this.startBaseMeleeMatchEnterRoomFromCurrPlayer(1, 10);
    }

    appendRoomMsg(msg: string) {
        this.ShowRoomMsg.string += `\r\n${msg}`;
        this.ShowRoomMsg.updateRenderData(true);
        this.ShowRoomMsgScrollView.scrollToBottom(0.3);
    }


    async onSendRoomMsgClick() {
        let editBox = this.InpRoomMsg;
        let msg = editBox.string;
        if (!msg) return;
        editBox.string = '';

        let ret = await Room.ins.sendRoomMsg({
            recvType: ERoomMsgRecvType.ROOM_ALL,
            msg: msg,
        });
        if (!ret.succ) {
            return this.showTips(ret.err);
        }
    }

    async onLeaveRoomClick() {
        await Room.ins.leaveRoom();
        await this.exitRoom();
    }
    async onDismissRoomClick() {
        await Room.ins.dismissRoom();
        await this.exitRoom();
    }

    /**请求已经进入房间了,这里执行进入房间流程(显示房间界面和信息,如果房间游戏已经开始会自动进入游戏流程)*/
    async enterRoom() {
        this.ShowRoomId.string = Room.ins.currRoomInfo!.roomId;
        this.ShowRoomPlayers.string = Room.ins.currRoomInfo!.playerList.map(p => p.showName).join(';   ');
        this.ShowRoomMsg.string = '';

        if (Room.ins.currRoomInfo?.ownerPlayerId === this.playerId) {
            //给房主显示解散房间按钮
            this.BtnDismissRoom.active = true;
            this.BtnLeaveRoom.active = false;
        } else {
            //其他人显示离开房间
            this.BtnDismissRoom.active = false;
            this.BtnLeaveRoom.active = true;
        }
        //进入房间界面
        this.viewStep(3);

        //判断房间的游戏状态
        if (Room.ins.currRoomInfo!.frameSyncState === EFrameSyncState.START) {
            //如果房间已经开始游戏了,则开始追帧
            let afterFramesRet = await Room.ins.requestAfterFrames();
            this.onStartGame(afterFramesRet.data!);
        }
    }
    /**请求数据都退出房间了, 这里执行退出房间流程, 回到大厅界面*/
    async exitRoom() {
        await this.stopGame();

        this.viewStep(2);
    }

    async onStartGameClick() {
        //开始帧同步
        await Room.ins.startFrameSync();
    }
    async onExitGameClick() {
        await this.stopGame();
    }

    /**仅停止游戏,界面还在游戏中,玩家还在房间中*/
    async stopGame() {
        if (Room.ins.currRoomInfo?.frameSyncState === EFrameSyncState.START) {
            //如果在帧同步中,则先停止
            await Room.ins.stopFrameSync();
        }
    }

    async onStopGame() {
        this.viewStep(3);

        this.frameSyncExecutor.stopExecuteFrame();

        this.PlayersContainer?.removeAllChildren();
    }

    async onStartGame(afterFrames?: IAfterFrames) {

        this.PlayersContainer?.removeAllChildren();
        this.GameObjFlagUI?.removeAllChildren();

        this.frameSyncExecutor.startExecuteFrame(Room.ins.currRoomInfo!.frameRate!, afterFrames);
        this.viewStep(4);
    }

    async onBtnAddRobot() {
        const ret = await Room.ins.createRoomRobot({
            showName: '机器人' + Math.floor(Math.random() * 1000),
        });
        if (!ret.succ) return this.showTips(ret.err);
    }
    async onBtnRemoveRobot() {
        if (!Room.ins.myPlayerInfo.roomRobotIds?.length) return this.showTips('你当前没有机器人');
        const allRobotPId = [...Room.ins.myPlayerInfo.roomRobotIds];
        const targetRobotPId = allRobotPId[Math.floor(Math.random() * allRobotPId.length)];

        const ret = await Room.ins.roomRobotLeave(targetRobotPId);
        if (!ret.succ) return this.showTips(ret.err);
    }
    async onBtnRobotSendMsg() {
        if (!Room.ins.myPlayerInfo.roomRobotIds?.length) return this.showTips('你当前没有机器人');
        const allRobotPId = [...Room.ins.myPlayerInfo.roomRobotIds];
        const targetRobotPId = allRobotPId[Math.floor(Math.random() * allRobotPId.length)];

        const ret = await Room.ins.sendRoomMsg({
            recvType: ERoomMsgRecvType.ROOM_ALL,
            msg: `I'm robot`,
        }, targetRobotPId);
        if (!ret.succ) return this.showTips(ret.err);
    }
    async onBtnRobotSendInputFrame() {
        if (!Room.ins.myPlayerInfo.roomRobotIds?.length) return this.showTips('你当前没有机器人');
        const allRobotPId = [...Room.ins.myPlayerInfo.roomRobotIds];
        const targetRobotPId = allRobotPId[Math.floor(Math.random() * allRobotPId.length)];

        //随机一个方向开始移动
        await Room.ins.sendFrame([
            {
                inputType: InputType.MoveDirStart,
                signRadFromX: Math.random() * 2 * Math.PI,
            }
        ], targetRobotPId);
        //1秒后停下来
        setTimeout(async () => {
            await Room.ins.sendFrame([
                {
                    inputType: InputType.MoveDirEnd,
                }
            ], targetRobotPId);
        }, 1000);
    }


    lateUpdate() {
        if (this.myPlayer && this.myPlayer.isValid) {
            //更新摄像机
            this.MainCamera?.node.setPosition(
                this.myPlayer.node.position.x,
                this.myPlayer.node.position.y + 30,
                this.myPlayer.node.position.z + 40);
            this.MainCamera?.node.lookAt(this.myPlayer.node.position);
        }
    }

    onNewPlayer(playerId: string, playerInfo: IPlayerInfo, dt: number): void {
        const p = new PlayerData();
        p.playerId = playerId;
        p.showName = playerInfo.showName;
        this.allPlayers[playerId] = p;

        var newPlayerName = instantiate(this.PlayerNameFlagPrefab!);
        newPlayerName.name = "PlayerNameId_" + playerId;
        let newPlayerNameFlag = newPlayerName.getComponent(PlayerNameFlagComponent)!;
        this.GameObjFlagUI?.addChild(newPlayerName);
        var newPlayer = instantiate(this.PlayerPrefab!);
        newPlayer.name = "PlayerId_" + playerId;
        var playerComp = newPlayer.getComponent(PlayerComponent)!;
        playerComp.init(p, this.MainCamera, newPlayerName);
        playerComp.playerNameFlag = newPlayerNameFlag;
        this.PlayersContainer?.addChild(newPlayer);

        if (playerId == this.playerId) {
            // 保存当前玩家渲染组件,在渲染层时方便使用(注意,不能在逻辑中使用!)
            this.myPlayer = playerComp;
        }
    }
    onRemovePlayer(playerId: string, dt: number): void {
        const p = this.allPlayers[playerId];
        if (!p) return;
        delete this.allPlayers[playerId];

        var playerNode = this.PlayersContainer?.getChildByName("PlayerId_" + p.playerId);
        let comp = playerNode?.getComponent(PlayerComponent);
        comp?.playerNameFlag.node.parent?.removeChild(comp?.playerNameFlag.node);
        if (playerNode) this.PlayersContainer?.removeChild(playerNode);
        playerNode?.destroy();
    }

    // 特殊输入帧, 由tsgf定义和下发
    execInputOthers(playerId: string, inputFrame: IFramePlayerInput, dt: number, FrameIndex: number) {
        switch (inputFrame.inputFrameType) {
            case EPlayerInputFrameType.PlayerEnterGame:
                //开始游戏时,房间中的玩家都触发一次
                this.onNewPlayer(playerId, inputFrame.playerInfo, dt);
                break;
            case EPlayerInputFrameType.JoinRoom:
                //游戏开始后再加入的玩家
                this.onNewPlayer(playerId, inputFrame.playerInfo, dt);
                break;
            case EPlayerInputFrameType.LeaveRoom:
                //游戏开始后再离开的玩家
                this.onRemovePlayer(playerId, dt);
                break;
        }
    }
    // 所有的输入帧, 推荐都只修改数据层, 而不动渲染层,有需要推荐通过事件方式通知, 是最佳做法
    // 逻辑中不能有"我"的概念, 因为可能本帧是其他玩家的输入帧!
    execInputOperates_MoveDirStart(playerId: string, inputFrame: IPlayerInputOperate, dt: number): void {
        // 移动开始, 修改玩家数据的朝向和移动状态(PlayerData), 注意, 这里不修改渲染数据(Cocos节点数据)
        const player = this.allPlayers[playerId] as PlayerData;
        if (!player) return;
        player.inMoving = true;
        player.moveSignRadFromX = inputFrame.signRadFromX;
        player.lastDir.set(Vec2.UNIT_X);
        player.lastDir.rotate(player.moveSignRadFromX);
    }
    execInputOperates_MoveDirEnd(playerId: string, inputFrame: IPlayerInputOperate, dt: number): void {
        // 移动结束, 修改玩家数据的朝向和移动状态(PlayerData), 注意, 这里不修改渲染数据(Cocos节点数据)
        const player = this.allPlayers[playerId];
        if (!player) return;
        player.inMoving = false;
    }

    // 每个逻辑帧的计算
    executeOneFrame(dt: number, frameIndex: number): void {
        this.frameIndex = frameIndex;
        // 因为本demo比较简单, 只有移动需要每帧计算位置, 所以简单一个循环, 完善的项目应该配套一个状态管理系统,根据每个对象的状态去计算下个状态应该的数据修改
        for (var playerId in this.allPlayers) {
            var p = this.allPlayers[playerId];
            this.playerUpdate(p, dt);
        }
        // 在玩家的渲染组件(PlayerComponent) 会在渲染循环中(update)去刷新渲染数据(比如模型节点的position), 逻辑帧中不推荐直接修改渲染数据, 但稍微复杂一点的游戏逻辑, 都会有和逻辑有关的渲染动作, 比如角色移动到某个位置发了一个技能, 渲染层分离后, 可以这么做:
        //   逻辑层更新位置, 发送技能动作照样修改逻辑数据, 多触发一个"发动某技能"的事件, 渲染层采用过渡方式更新渲染, 收到这个事件后, 可以简单的判断位置瞬移过来, 也可以将渲染动作设计为一个渲染队列, 移动 / 旋转 / 发技能, 都加入队列, 一样采用过渡方式渲染, 过渡时间为20ms, 这样既可以最短的方式准确响应预期动作, 也可以得到最顺畅的渲染效果
    }
    playerUpdate(player: PlayerData, dt: number) {
        if (player.inMoving) {
            //有移动,转向直接生效,小方块,懒得转忽略
            var distance = player.speed * dt;//本帧移动的距离
            //根据方向,算出本帧移动向量
            Vec2.multiplyScalar(this.tmpV2, player.lastDir, distance);
            //加到老坐标
            Vec2.add(player.pos, player.pos, this.tmpV2);
        }
    }


    // 发送输入帧, 注意,所有逻辑修改入口必须来自收到的同步帧, 而本地想要修改, 就必须发送输入帧!
    onJoysickMoveStart(move: IMoveDirection) {
        Room.ins.sendFrame([
            {
                inputType: InputType.MoveDirStart,
                signRadFromX: move.signRadFromX,
            }
        ]);
    }
    onJoysickMoveEnd() {
        Room.ins.sendFrame([
            {
                inputType: InputType.MoveDirEnd,
            }
        ]);
    }
}